import mpd

from datetime import datetime
from functools import wraps, total_ordering, partial

###############################################################################
## Just change the below settings to make the MPD module become stand-alone. ##
###############################################################################

from lib import safe_unicode, safe_str
from settings import settings
from xdh_logging import initLog, LogWriter

log = initLog("MPD")    # Set this to a logger instance.

_connection_settings = {
	'host': settings.mpd.host,
	'port': settings.mpd.port,
	'timeout': settings.mpd.timeout,
	'use_unicode': settings.mpd.use_unicode
}

_config = {setting: value for setting, value in settings.mpd.config.__dict__.iteritems()}

###############################################################################

class _REPLAY_GAIN(object):
	off = "off"
	track = "track"
	album = "album"
	auto = "auto"

class _SUBSYSTEMS(object):
	database = "database"
	update = "update"
	stored_playlist = "stored_playlist"
	playlist = "playlist"
	player = "player"
	mixer = "mixer"
	output = "output"
	options = "options"
	sticker = "sticker"
	subscription = "subscription"
	message = "message"

class _decoders(object):
	pass


class _decoder(object):
	pass
        
class _MPDClient(mpd.MPDClient):
	def __init__(self, use_unicode=False):
		mpd.MPDClient.__init__(self, use_unicode)

	def __enter__(self):
		return self

	def __exit__(self, type, value, traceback):
		self.disconnect()

	@classmethod
	def _connect(cls, host, port, timeout=None, use_unicode=False):
		ret = cls(use_unicode)
		ret.connect(host, port, timeout)
		return ret


def _mpdwrap(method):
	@wraps(method)
	def wrapper(*args, **kwargs):
		with _MPDClient._connect(**_connection_settings) as conn:
			kwargs["conn"] = conn
			return method(*args, **kwargs)

	return wrapper

			
"""
My MPD API class. Designed to completely conceal the underlying interface to mpdlib2, and make a single, simplified
API to interact with MPD in a convenient manner.
"""
class MPD_API(object):

	@classmethod
	@_mpdwrap
	def mixrampdb(cls, deciBels, conn = None):
		conn.mixrampdb(deciBels)


	@classmethod
	@_mpdwrap
	def mixrampdelay(cls, seconds, conn = None):
		conn.mixrampdelay(seconds)


	@classmethod
	@_mpdwrap
	def setvol(cls, vol, conn = None):
		conn.setvol(vol)


	@classmethod
	@_mpdwrap
	def clearerror(cls, conn = None):
		conn.clearerror()


	@classmethod
	@_mpdwrap
	def idle(cls, args = None, conn = None):
		if args == None:
			conn.idle()
		else:
			to_pass = [arg for arg in args if arg in SUBSYSTEMS.__dict__]
			conn.idle(*to_pass)

	@classmethod
	@_mpdwrap
	def stats(cls, conn = None):
		return conn.stats()


	@classmethod
	@_mpdwrap
	def ping(cls, conn = None):
		return conn.ping()


	@classmethod
	@_mpdwrap
	def kill(cls, conn = None):
		return conn.kill()


	@classmethod
	@_mpdwrap
	def outputs(cls, conn = None):
		return conn.outputs()


	@classmethod
	@_mpdwrap
	def disableoutput(cls, output_id, conn = None):
		conn.disableoutput(output_id)


	@classmethod
	@_mpdwrap
	def enableoutput(cls, output_id, conn = None):
		conn.enableoutput(output_id)


	@classmethod
	@_mpdwrap
	def commands(cls, conn = None):
		return conn.commands()


	@classmethod
	@_mpdwrap
	def notcommands(cls, conn = None):
		return conn.notcommands()


	@classmethod
	@_mpdwrap
	def tagtypes(cls, conn = None):
		return conn.tagtypes()


	@classmethod
	@_mpdwrap
	def urlhandlers(cls, conn = None):
		return conn.urlhandlers()


	@classmethod
	@_mpdwrap
	def decoders(cls, conn = None):
		return conn.decoders()


	@classmethod
	@_mpdwrap
	def subscribe(cls, name, conn = None):
		conn.subscribe(name)


	@classmethod
	@_mpdwrap
	def unsubscribe(cls, name, conn = None):
		conn.unsubscribe(name)


	@classmethod
	@_mpdwrap
	def channels(cls, conn = None):
		return conn.channels()


	@classmethod
	@_mpdwrap
	def readmessages(cls, conn = None):
		return conn.readmessages()


	@classmethod
	@_mpdwrap
	def sendmessage(cls, channel, text, conn = None):
		return conn.sendmessage(channel, text)


	@classmethod
	@_mpdwrap
	def update(cls, path = None, conn = None):
		if path == None:
			conn.update()
		else:
			conn.update(path)

	@classmethod
	@_mpdwrap
	def rescan(cls, path = None, conn = None):
		if path == None:
			conn.rescan()
		else:
			conn.rescan(path)

	@classmethod
	@_mpdwrap
	def status(cls, conn = None):
		return conn.status()


	@classmethod
	@_mpdwrap
	def sticker_get(cls, type, uri, name, conn = None):
		return conn.sticker_get(type, uri, name)


	@classmethod
	@_mpdwrap
	def sticker_set(cls, type, uri, name, value, conn = None):
		return conn.sticker_set(type, uri, name, value)


	@classmethod
	@_mpdwrap
	def sticker_delete(cls, type, uri, name, conn = None):
		return conn.sticker_delete(type, uri, name)


	@classmethod
	@_mpdwrap
	def sticker_list(cls, type, uri, conn = None):
		return conn.sticker_list(type, uri)


	@classmethod
	@_mpdwrap
	def sticker_find(cls, type, uri, name, conn = None):
		return conn.sticker_find(type, uri, name)


	@classmethod
	@_mpdwrap
	def stop(cls, conn = None):
		conn.stop()


	@classmethod
	@_mpdwrap
	def playlistfind(cls, tag, needle, conn = None):
		return conn.playlistfind(tag, needle)


	@classmethod
	@_mpdwrap
	def playlistsearch(cls, tag, needle, conn = None):
		return conn.playlistsearch(tag, needle)


	@classmethod
	@_mpdwrap
	def plchanges(cls, version, conn = None):
		return conn.plchanges(version)


	@classmethod
	@_mpdwrap
	def plchangesposid(cls, version, conn = None):
		return conn.plchangesposid(version)


	@classmethod
	@_mpdwrap
	def prio(cls, priority, rng, conn = None):
		conn.prio(priority, rng)


	@classmethod
	@_mpdwrap
	def prioid(cls, priority, ids, conn = None):
		conn.prio(priority, *ids)


	@classmethod
	@_mpdwrap
	def command_list_ok_begin(cls, conn = None):
		conn.command_list_ok_begin()


	@classmethod
	@_mpdwrap
	def command_list_end(cls, conn = None):
		conn.command_list_end()


	@classmethod
	@_mpdwrap
	def add(cls, string, conn = None):
		conn.add(safe_unicode(string))


	@classmethod
	@_mpdwrap
	def addid(cls, string, pos = None, conn = None):
		if pos == None:
			return conn.addid(string)
		else:
			return conn.addid(string, pos)

	@classmethod
	@_mpdwrap
	def playlistinfo(cls, songpos = None, conn = None):
		if songpos == None:
			return conn.playlistinfo()
		else:
			return conn.playlistinfo(songpos)


	@classmethod
	@_mpdwrap
	def currentsong(cls, conn = None):
		return conn.currentsong()


	@classmethod
	@_mpdwrap
	def delete(cls, pos, conn = None):
		conn.delete(pos)


	@classmethod
	@_mpdwrap
	def deleteid(cls, songid, conn = None):
		conn.deleteid(songid)


	@classmethod
	@_mpdwrap
	def load(cls, name, conn = None):
		conn.load(name)


	@classmethod
	@_mpdwrap
	def save(cls, name, conn = None):
		conn.save(name)


	@classmethod
	@_mpdwrap
	def shuffle(cls, conn = None):
		conn.shuffle()


	@classmethod
	@_mpdwrap
	def swap(cls, pos1, pos2, conn = None):
		conn.swap(pos1, pos2)


	@classmethod
	@_mpdwrap
	def swapid(cls, songid1, songid2, conn = None):
		conn.swapid(songid1, songid2)


	@classmethod
	@_mpdwrap
	def rename(cls, name, new_name, conn = None):
		conn.rename(name, new_name)


	@classmethod
	@_mpdwrap
	def move(cls, pos_from, pos_to, conn = None):
		conn.move(pos_from, pos_to)


	@classmethod
	@_mpdwrap
	def moveid(cls, songid_from, pos_to, conn = None):
		conn.move(songid_from, pos_to)


	@classmethod
	@_mpdwrap
	def clear(cls, conn = None):
		conn.clear()


	@classmethod
	@_mpdwrap
	def crossfade(cls, seconds, conn = None):
		conn.crossfade(seconds)


	@classmethod
	@_mpdwrap
	def single(cls, do_single, conn = None):
		conn.single(1 if do_single else 0)


	@classmethod
	@_mpdwrap
	def consume(cls, do_consume, conn = None):
		conn.consume(1 if do_consume else 0)


	@classmethod
	@_mpdwrap
	def random(cls, do_random, conn = None):
		conn.random(1 if do_random else 0)


	@classmethod
	@_mpdwrap
	def repeat(cls, do_repeat, conn = None):
		conn.repeat(1 if do_repeat else 0)

	
	@classmethod
	@_mpdwrap
	def replay_gain_status(cls, conn = None):
		return conn.replay_gain_status()


	@classmethod
	@_mpdwrap
	def replay_gain_mode(cls, mode, conn = None):
		conn.replay_gain_mode(mode)

	
	@classmethod
	@_mpdwrap
	def seek(cls, pos, time, conn = None):
		conn.seek(pos, time)

	
	@classmethod
	@_mpdwrap
	def seekid(cls, songid, time, conn = None):
		conn.seekid(songid, time)


	@classmethod
	@_mpdwrap
	def seekcur(cls, time, conn = None):
		conn.seekcur(time)


	@classmethod
	@_mpdwrap
	def next(cls, conn = None):
		conn.next()


	@classmethod
	@_mpdwrap
	def previous(cls, conn = None):
		conn.previous()

	@classmethod
	@_mpdwrap
	def pause(cls, do_pause, conn = None):
		conn.pause(1 if do_pause else 0)

	
	@classmethod
	@_mpdwrap
	def play(cls, pos = -1, conn = None):
		conn.play(pos)


	@classmethod
	@_mpdwrap
	def playid(cls, songid = None, conn = None):
		if songid == None:
			conn.playid()

		else:
			conn.playid(songid)


	@classmethod
	@_mpdwrap
	def listplaylists(cls, conn = None):
		return conn.listplaylists()


	@classmethod
	@_mpdwrap
	def listplaylist(cls, name, conn = None):
		return conn.listplaylist(name)


	@classmethod
	@_mpdwrap
	def listplaylistinfo(cls, name, conn = None):
		return conn.listplaylistinfo(name)


	@classmethod
	@_mpdwrap
	def playlistadd(cls, name, uri, conn = None):
		conn.playlistadd(name, uri)


	@classmethod
	@_mpdwrap
	def playlistclear(cls, name, conn = None):
		conn.playlistclear(name)


	@classmethod
	@_mpdwrap
	def playlistdelete(cls, name, songpos, conn = None):
		conn.playlistdelete(name, songpos)


	@classmethod
	@_mpdwrap
	def playlistmove(cls, name, songid, songpos, conn = None):
		conn.playlistmove(name, songid, songpos)


	@classmethod
	@_mpdwrap
	def rm(cls, name, conn = None):
		conn.rm(name)


	@classmethod
	@_mpdwrap
	def count(cls, tag, needle, conn = None):
		return conn.count(tag, needle)


	@classmethod
	@_mpdwrap
	def find(cls, type, what, conn = None):
		return conn.find(type, what)


	@classmethod
	@_mpdwrap
	def findadd(cls, type, what, conn = None):
		conn.findadd(type, what)


	@classmethod
	@_mpdwrap
	def search(cls, type, what, conn = None):
		return conn.search(type, what)


	@classmethod
	@_mpdwrap
	def searchadd(cls, type, what, conn = None):
		conn.searchadd(type, what)


	@classmethod
	@_mpdwrap
	def searchaddpl(cls, name, type, what, conn = None):
		conn.searchaddpl(name, type, what)


	@classmethod
	@_mpdwrap
	def list(cls, type, artist = None, conn = None):
		if artist == None:
			return conn.list(type)
		else:
			return conn.list(type, artist)


	@classmethod
	@_mpdwrap
	def listall(cls, uri = None, conn = None):
		if uri == None:
			return conn.listall()
		else:
			return conn.listall(uri)


	@classmethod
	@_mpdwrap
	def listallinfo(cls, uri = None, conn = None):
		if uri == None:
			return conn.listallinfo()
		else:
			return conn.listallinfo(uri)


	@classmethod
	@_mpdwrap
	def lsinfo(cls, uri = None, conn = None):
		if uri == None:
			return conn.lsinfo()
		else:
			return conn.lsinfo(uri)

def tagged(cls):
	def get_tag(this, tag):
		info = MPD_API.find("file", this.filename)[0]
        
		if tag in info:
			return info[tag]
        
		else:
			return None
        
	for attr in ['Artist', 'Album', 'AlbumArtist', 'Title', 'Track', 'Name', 'Genre', 'Date', 'Composer', 'Performer', 'Disc', 'MUSICBRAINZ_ARTISTID', 'MUSICBRAINZ_ALBUMID', 'MUSICBRAINZ_ALBUMARTISTID', 'MUSICBRAINZ_TRACKID', 'Comment']:
        
		if attr.lower() not in ('albumartistsort', 'artistsort'):
        
			doc_str = "Gets the {tag} tag for this track".format(tag = attr)
        
        
			setattr(cls, attr.lower(), property(fget = partial(get_tag, tag = attr.lower()), doc = doc_str))
        
	return cls

@total_ordering
class Stickers(object):
	def __init__(self, track):
		self.track = track

	def __getitem__(self, name):
		"""
		Gets a single sticker from the track.
		"""
		return MPD_API.sticker_get("song", self.track, name).split("=")[1]


	def __setitem__(self, name, value):
		"""
		Puts a single sticker on the track.
		"""
		MPD_API.sticker_set("song", self.track, name, value)


	def __delitem__(self, name):
		"""
		Removes a single sticker from the track.
		"""
		MPD_API.sticker_delete("song", self.track, name)


	@property
	def _all(self):
		"""
		Hidden, helps with making everything work like a dict.
		"""
		stickerlist = MPD_API.sticker_list("song", self.track)

		ret = {}

		for data in stickerlist:
			name, value = data.split("=")
			ret[name] = value

		return ret


	def __contains__(self, name):
		return name in self._db


	def __str__(self):
		"""
		String form of all of the stickers on this track.
		"""
		return str(self._all)


	def __repr__(self):
		"""
		String representation of a dict containing all of the stickers on this track.
		"""
		return repr(self._all)


	def __eq__(self, other):
		if isinstance(other, Stickers):
			return self._all == other._all

		else:
			return self._all == other


	def __ne__(self, other):
		if isinstance(other, Stickers):
			return self._all != other._all

		else:
			return self._all != other


	def __ge__(self, other):
		if isinstance(other, Stickers):
			return self._all >= other._all

		else:
			return self._all >= other


	def keys(self):
		"""
		Returns the names of all of the stickers on this track.
		"""
		return self._all.keys()


	def values(self):
		"""
		Returns all of the values of the stickers on this track.
		"""
		return self._all.values()


	def items(self):
		"""
		Returns (name, value) for each sticker on this track.
		"""
		return self._all.items()


	def has_key(self, name):
		"""
		Tests if the name is used for a sticker on this track.
		"""
		return self._all.has_key(name)


	def get(self, name):
		"""
		Gets a single sticker from this track.
		"""
		return self._all.get(name)


	def clear(self):
		"""
		Removes all stickers on this track.
		"""
		for name in self._all:
			self.__delitem__(name)


	def iterkeys(self):
		"""
		Returns an iterator over all of the names of the stickers on this track.
		"""
		return self._all.iterkeys()


	def itervalues(self):
		"""
		Returns an iterator over all of the values of the stickers on this track.
		"""
		return self._all.itervalues()


	def iteritems(self):
		"""
		Returns an iterator over all of the (name, value) pairs for the stickers
		on this track.
		"""
		return self._all.iteritems()


	def __iter__(self):
		"""
		Returns an iterator over all of the names of the stickers on this track.
		"""
		return self.iterkeys()


	def __len__(self):
		return len(self._all)


	def update(self, sticker_dict):
		for key, value in sticker_dict.iteritems():
			self[key] = value
@tagged
@total_ordering
class Track(object):
	def __init__(self, filename):
		if filename == None:
			self._filename = None
		else:
			self._filename = safe_unicode(filename)

	def __unicode__(self):
		return safe_unicode(self.filename)

	def __str__(self):
		return safe_str(self.filename)

	def __repr__(self):
		return repr(self.filename)

	def __lt__(self, other):
		if other == None or self.filename == None:
			return False
		return self.filename < safe_unicode(other)


	def __eq__(self, other):
		if other == None and self.filename == None:
			return True
		elif other == None or self.filename == None:
			return False
		return self.filename == safe_unicode(other)


	def __ne__(self, other):
		if other == None and self.filename == None:
			return False
		elif other == None or self.filename == None:
			return True
		return self.filename != safe_unicode(other)

	def __le__(self, other):
		if other == None and self.filename == None:
			return True
		elif other == None or self.filename == None:
			return False
		return self.filename <= safe_unicode(other)

	def __hash__(self):
		return hash(unicode(self))

	@property
	def filename(self):
		"""
		Returns the filename for this track.
		"""
		return self._filename

	@property
	def last_modified(self):
		"""
		Gets the last modified date/time for this track
		"""
		info = MPD_API.find("file", self.filename)[0]
		return datetime.strptime(info["last-modified"], "%Y-%m-%dT%H:%M:%SZ")


	@property
	def time(self):
		"""
		Gets the time tag for this track
		"""
		info = MPD_API.find("file", self.filename)[0]

		return int(info["time"])


	@property
	def stickers(self):
		if not hasattr(self, "_stickers"):
                
                
			self._stickers = Stickers(self)

		return self._stickers

class CommandList(mpd.MPDClient):
	def __init__(self, inst):
		self.inst = inst
		mpd.MPDClient.__init__(self, _connection_settings['use_unicode'])

		real_settings = dict(_connection_settings)

		del real_settings['use_unicode']

		self.connect(**real_settings)

	def __enter__(self):
		self.inst._conn = self

		self.command_list_ok_begin()

	def __exit__(self, type, value, traceback):
		self.command_list_end()

		del self.inst._conn

class Database(object):
	def __init__(self, mpd):
		self.mpd = mpd

	@property
	def _db(self):
		"""
		Property that has the list of all tracks, each track is represented as a string containing
                the path & filename, respective to the MPD music library path in its configuration.
		"""

		return [self.mpd._track(filename) for filename in self.mpd._conn.list("file")]


	def __contains__(self, track):
		return len(self.find("file", track)) > 0


	def __len__(self):
		return len(self._db)


	def __str__(self):
		"""
		Returns the string version of the DB's set of tracks.
		"""
		return unicode(self._db)


	def __repr__(self):
		"""
		Returns a string representation of the full DB.
		"""
		return repr(self._db)


	def __getitem__(self, index):
		"""
		Gets one track from the DB.
		"""
		return self._db[index]


	def count(self, tag, needle):
		"""
		Counts the number of tracks and their total playtime in the DB matching with their <tag> matching <needle> exactly.  <tag> equals one of the properties in MPD.TAG_TYPES.
		"""
		return self.mpd._conn.count(tag, needle)


	def find(self, type, what, add = False, strict = True):
		"""
		Finds songs in the DB that are exactly <what>. <type> equals one of the properties in MPD.FIND_TYPES.

		if the optional parameter of strict is set to False, this becomes less restrictive, matching songs in the DB that are like <what>, case-insensitive.

		if the optional parameter of add is set to True, the found tracks are added to the current playlist, if it is set to a playlist name, they are added to that
		ExternalPlaylist instead.
		"""

		if strict:
			tracks = self.mpd._conn.find(type, what)
		else:
			tracks = self.mpd._conn.search(type, what)

		tracks = [self.mpd._track(track["file"]) for track in tracks]

		if isinstance(add, basestring):
			tracks = self.mpd._conn.search(type, what)
			self.mpd.playlists[add].extend(tracks)
		elif add:
			self.mpd.extend(tracks)

		return tracks


	def list(self, type, artist = None):
		"""
		Lists all tags of the specified <type>. <type> equals one of the properties in MPD.LIST_TYPES.
		<artist> is an optional parameter when <type> is MPD.LIST_TYPES.album, this specifies to list albums by an artist.
		"""
		if type == self.mpd.LIST_TYPES.album and artist != None:
			tracks = self.mpd._conn.list(type, artist)

		else:
			tracks = self.mpd._conn.list(type)

		return [self.mpd._track(filename) for filename in tracks]


	def listall(self, uri = None):
		"""
		Lists all songs and directories in <uri>
		"""
		if uri != None:
			ret = self.mpd._conn.listall(url)

		else:
			ret = self.mpd._conn.listall()


		for item in ret:
			if "file" in item:
				item["file"] = self.mpd._track(item["file"])


		return ret
                
                
@total_ordering
class ExternalPlaylist(object):
	"""
	Wraps a single External Playlist into a convenient object.
	"""
	def __init__(self, name, mpd):
		self.mpd = mpd
		self._name = name


	@property
	def name(self):
		"""
		The name of this playlist (read-only)
		"""
		return self._name


	def __getitem__(self, index):
		"""
		Gets one track from the playlist.
		"""
		return self.mpd._track(self.mpd._conn.listplaylistinfo(self.name)[index])


	def __setitem__(self, index, track):
		"""
		Places one track into the playlist at the given index.
		It *will* overwrite an existing track at the same index.
		"""
		with self.mpd.command_list:
			self.mpd._conn.playlistdelete(self.name, index)
			self.mpd._conn.playlistadd(self.name, track)
			self.mpd._conn.playlistmove(self.name, self.__len__() - 1, index)


	def __delitem__(self, index):
		"""
		Deletes one item from the playlist at the given index.
		"""
		self.mpd._conn.playlistdelete(self.name, index)


	def __len__(self):
		"""
		Returns the length of the playlist.
		"""
		return len(self.mpd._conn.listplaylist(self.name))


	@property
	def _playlist(self):
		return [self.mpd._track(filename) for filename in self.mpd._conn.listplaylist(self.name)]


	def __iter__(self):
		"""
		Returns an iterator over the playlist.
		"""
		return self._playlist.__iter__()


	def __repr__(self):
		"""
		Shows the repr of the playlist.
		"""
		return self._playlist.__repr__()


	def __str__(self):
		"""
		Shows the str version of the playlist.
		"""
		return self._playlist.__str__()


	def __contains__(self, track):
		"""
		Tests if the given track is in the playlist.
		"""
		return track in self.mpd._conn.listplaylist(self.name)


	def clear(self):
		"""
		Erases all of the tracks from the playlist.
		"""
		self.mpd._conn.playlistclear(self.name)


	def append(self, track):
		"""
		Adds one track to the end of the playlist.
		"""
		self.mpd._conn.playlistadd(self.name, track)


	def count(self, track):
		"""
		Counts the number of occurences of the given track in the playlist.
		"""
		return self.mpd._conn.listplaylist(self.name).count(track)


	def extend(self, tracks):
		"""
		Adds the list of tracks to the end of the playlist.
		"""
		for track in tracks:
			self.append(track)


	def index(self, track, i = 0, j = -1):
		"""
		Returns the index of the given track in the playlist.
		"""
		if j < 0:
			j += len(self)

			if j < 0:
				j = 0

		if i < 0:
			i += len(self)

			if i < 0:
				i = 0

		return self.mpd._conn.listplaylist(self.name).index(track, i, j)


	def remove(self, track):
		"""
		Removes the track from the playlist.
		"""
		del self[self.index(track)]


	def __hash__(self):
		"""
		Returns the hash code for this playlist, based on the name.
		"""
		return self.name.__hash__()


	def __eq__(self, that):
		"""
		Tests if this == that. "that" can be either another ExternalPlaylist,
		an External Playlist name, or some other collection.
		"""
		if isinstance(that, ExternalPlaylist):
			return conn.listplaylist(self.name) == conn.listplaylist(that.name)

		elif isinstance(that, basestring):
			return conn.listplaylist(self.name) == conn.listplaylist(that)

		else:
			return conn.listplaylist(self.name) == that


	def __ne__(self, that):
		"""
		Tests if this != that. "that" can be either another ExternalPlaylist,
		an External Playlist name, or some other collection.
		"""
		if isinstance(that, ExternalPlaylist):
			return conn.listplaylist(self.name) != conn.listplaylist(that.name)

		elif isinstance(that, basestring):
			return conn.listplaylist(self.name) != conn.listplaylist(that)

		else:
			return conn.listplaylist(self.name) != that


	def __ge__(self, that):
		"""
		Tests if this >= that. "that" can be either another ExternalPlaylist,
		an External Playlist name, or some other collection.
		"""
		if isinstance(that, ExternalPlaylist):
			return conn.listplaylist(self.name) >= conn.listplaylist(that.name)

		elif isinstance(that, basestring):
			return conn.listplaylist(self.name) >= conn.listplaylist(that)

		else:
			return conn.listplaylist(self.name) >= that


class Playlists(object):
	"""
	Wraps the external playlists into a dictionary-like object.

	See help(MPD.playlists["<name>"]) for further information, replacing <name> with the name of
	an external playlist.
	"""
	def __init__(self, mpd):
		self.mpd = mpd

	def __getitem__(self, name):
		"""
		Gets the ExternalPlaylist object for the given name.
		"""
		return ExternalPlaylist(name, self.mpd)


	def __delitem__(self, name):
		"""
		Deletes the External Playlist. Note, this deletes the file from the MPD playlist folder!
		"""
		self.mpd._conn.rm(name)


	def __iter__(self):
		"""
		Iterates over the list of ExternalPlaylists.
		"""
		return self._data.__iter__()


	@property
	def _data(self):
		"""
		Contains the dictionary representation of the full set of External Playlists.
		"""
		ret = {}
			
		for playlist in self.mpd._conn.listplaylists():
			ret[playlist["playlist"]] = ExternalPlaylist(playlist["playlist"], self.mpd)

		return ret

	def __len__(self):
		"""
		Returns the number of defined External Playlists.
		"""
		return len(self.mpd._conn.listplaylists())


	def __repr__(self):
		"""
		Same as _data.__repr__()
		"""

		return self._data.__repr__()


	def __str__(self):
		"""
		Same as _data.__str__()
		"""
		return self._data.__str__()


	def keys(self):
		"""
		Returns the list of keys (names) of the External Playlists.
		"""
		return self._data.keys()


	def values(self):
		"""
		Returns the list of values (ExternalPlaylists) of the External Playlists.
		"""
		return self._data.values()


	def items(self):
		"""
		Returns two-element tuples (name, ExternalPlaylist) of the External Playlists.
		"""
		return self._data.items()


	def has_key(self, name):
		"""
		Tests if the given name exists as an External Playlist.
		"""
		return name in [playlist["playlist"] for playlist in self.mpd._conn.listplaylists()]


	def iterkeys(self):
		"""
		Returns an iterator over the playlist names.
		"""
		return self._data.iterkeys()


	def get(self, name):
		"""
		Returns the ExternalPlaylist for the given name.
		"""
		return self[name]


	def clear(self):
		"""
		Deletes ALL of the external playlists. Note: This deletes the files from the
		MPD playlist directory!
		"""
		for playlist in self.mpd._conn.listplaylists():
			del self[playlist["playlist"]]


	def itervalues(self):
		"""
		Returns an iterator over the set of ExternalPlaylist instances.
		"""
		return self._data.itervalues()


	def setdefault(self, key, default = None):
		"""
		Not implemented.
		"""
		return NotImplemented


	def update(self, other = None):
		"""
		Not implemented.
		"""
		return NotImplemented


	def iteritems(self):
		"""
		Returns an iterator over the list of two-element tuples (name, ExternalPlaylist)
		for all of the playlists in the MPD playlist directory.
		"""
		return self._data.iteritems()


	def pop(self, key, default = None):
		"""
		Not implemented
		"""
		return NotImplemented


	def popitem(self):
		"""
		Not implemented
		"""
		return NotImplemented


	def copy(self):
		"""
		Not implemented
		"""
		return NotImplemented


	def __contains__(self, name):
		"""
		Tests if the given name exists in the MPD playlist directory.
		"""
		return self.has_key(name)
                        
                        
@total_ordering
class Output(object):
	"""
	Wrapper class to completely encompass a MPD output.

	This is designed to emulate a boolean value, and supports all of the standard numeric
	operations.
	"""
	def __init__(self, id, mpd):
		self.mpd = mpd
		self._id = id

	
	@property
	def name(self):
		"""
		Returns the name of this output.
		"""
		return self.mpd._conn.outputs()[self.id]["outputname"]


	@property
	def id(self):
		"""
		Returns the ID of this output.
		"""
		return self._id


	@property
	def enabled(self):
		"""
		Returns the enabled setting for this output.

		Can be set by simple assignment:

		output.enabled = True

		will enable this output.
		"""
		return int(self.mpd._conn.outputs()[self.id]["outputenabled"]) == 1

	@enabled.setter
	def enabled(self, enable):
		if not enable:
			self.mpd._conn.disableoutput(self.id)
		else:
			self.mpd._conn.enableoutput(self.id)


	def __eq__(self, that):
		if isinstance(that, Output):
			return self.enabled == that.enabled

		else:
			return self.enabled == that


	def __ne__(self, that):
		if isinstance(that, Output):
			return self.enabled != that.enabled

		else:
			return self.enabled != that


	def __ge__(self, that):
		if isinstance(that, Output):
			return self.enabled >= that.enabled

		else:
			return self.enabled >= that


	def __hash__(self):
		return self.name.__hash__()


	def __repr__(self):
		return '"{name}": {enabled}'.format(name = self.name, enabled = self.enabled)


	def __str__(self):
		return '{name}: {enabled}'.format(name = self.name, enabled = self.enabled)


	def __add__(self, that):
		if isinstance(that, Output):
			return self.enabled + that.enabled

		else:
			return self.enabled + that


	def __sub__(self, that):
		if isinstance(that, Output):
			return self.enabled - that.enabled

		else:
			return self.enabled - that


	def __mul__(self, that):
		if isinstance(that, Output):
			return self.enabled * that.enabled

		else:
			return self.enabled * that


	def __floordiv__(self, that):
		if isinstance(that, Output):
			return self.enabled // that.enabled

		else:
			return self.enabled // that


	def __mod__(self, that):
		if isinstance(that, Output):
			return self.enabled % that.enabled

		else:
			return self.enabled % that


	def __divmod__(self, that):
		if isinstance(that, Output):
			return divmod(self.enabled, that.enabled)

		else:
			return divmod(self.enabled, that)


	def __pow__(self, that, modulo = None):
		if isinstance(that, Output):
			if modulo == None:
				return self.enabled ** that.enabled
			else:
				return pow(self.enabled, that.enabled, modulo)

		else:
			if modulo == None:
				return self.enabled ** that
			else:
				return pow(self.enabled, that, modulo)


	def __lshift__(self, that):
		if isinstance(that, Output):
			return self.enabled << that.enabled

		else:
			return self.enabled << that


	def __rshift__(self, that):
		if isinstance(that, Output):
			return self.enabled >> that.enabled

		else:
			return self.enabled >> that


	def __and__(self, that):
		if isinstance(that, Output):
			return self.enabled & that.enabled

		else:
			return self.enabled & that


	def __xor__(self, that):
		if isinstance(that, Output):
			return self.enabled ^ that.enabled

		else:
			return self.enabled ^ that


	def __or__(self, that):
		if isinstance(that, Output):
			return self.enabled | that.enabled

		else:
			return self.enabled | that


	def __div__(self, that):
		if isinstance(that, Output):
			return self.enabled.__div__(that.enabled)

		else:
			return self.enabled.__div__(that)


	def __truediv__(self, that):
		if isinstance(that, Output):
			return self.enabled.__truediv__(that.enabled)

		else:
			return self.enabled.__truediv__(that)


	def __radd__(self, that):
		if isinstance(that, Output):
			return that.enabled + self.enabled

		else:
			return that + self.enabled


	def __rsub__(self, that):
		if isinstance(that, Output):
			return that.enabled - self.enabled

		else:
			return that - self.enabled


	def __rmul__(self, that):
		if isinstance(that, Output):
			return that.enabled * self.enabled

		else:
			return that * self.enabled


	def __rdiv__(self, that):
		if isinstance(that, Output):
			return that.enabled.__div__(self.enabled)

		else:
			return that.__div__(self.enabled)


	def __rtruediv__(self, that):
		if isinstance(that, Output):
			return that.enabled.__truediv__(self.enabled)

		else:
			return that.__truediv__(self.enabled)


	def __rfloordiv__(self, that):
		if isinstance(that, Output):
			return that.enabled // self.enabled

		else:
			return that // self.enabled


	def __rmod__(self, that):
		if isinstance(that, Output):
			return that.enabled % self.enabled

		else:
			return that % self.enabled


	def __rdivmod__(self, that):
		if isinstance(that, Output):
			return divmod(that.enabled, self.enabled)

		else:
			return divmod(that, self.enabled)


	def __rpow__(self, that, modulo = None):
		if isinstance(that, Output):
			if modulo == None:
				return that.enabled ** self.enabled
			else:
				return pow(that.enabled, self.enabled, modulo)

		else:
			if modulo == None:
				return that ** self.enabled
			else:
				return pow(that, self.enabled, modulo)


	def __rlshift__(self, that):
		if isinstance(that, Output):
			return that.enabled << self.enabled

		else:
			return that << self.enabled


	def __rrshift__(self, that):
		if isinstance(that, Output):
			return that.enabled >> self.enabled

		else:
			return that >> self.enabled


	def __rand__(self, that):
		if isinstance(that, Output):
			return that.enabled & self.enabled

		else:
			return that & self.enabled


	def __rxor__(self, that):
		if isinstance(that, Output):
			return that.enabled ^ self.enabled

		else:
			return that ^ self.enabled


	def __ror__(self, that):
		if isinstance(that, Output):
			return that.enabled | self.enabled

		else:
			return that | self.enabled


	def __iadd__(self, that):
		if isinstance(that, Output):
			self.enabled += that.enabled

		else:
			self.enabled += that

		return self


	def __isub__(self, that):
		if isinstance(that, Output):
			self.enabled -= that.enabled

		else:
			self.enabled -= that

		return self


	def __imul__(self, that):
		if isinstance(that, Output):
			self.enabled *= that.enabled

		else:
			self.enabled *= that

		return self


	def __ifloordiv__(self, that):
		if isinstance(that, Output):
			self.enabled //= that.enabled

		else:
			self.enabled //= that

		return self


	def __imod__(self, that):
		if isinstance(that, Output):
			self.enabled %= that.enabled

		else:
			self.enabled %= that

		return self


	def __ipow__(self, that):
		if isinstance(that, Output):
			self.enabled **= that.enabled

		else:
			self.enabled **= that

		return self


	def __ilshift__(self, that):
		if isinstance(that, Output):
			self.enabled <<= that.enabled

		else:
			self.enabled <<= that

		return self


	def __irshift__(self, that):
		if isinstance(that, Output):
			self.enabled >>= that.enabled

		else:
			self.enabled >>= that

		return self


	def __iand__(self, that):
		if isinstance(that, Output):
			self.enabled &= that.enabled

		else:
			self.enabled &= that

		return self


	def __ixor__(self, that):
		if isinstance(that, Output):
			self.enabled ^= that.enabled

		else:
			self.enabled ^= that

		return self


	def __ior__(self, that):
		if isinstance(that, Output):
			self.enabled |= that.enabled

		else:
			self.enabled |= that

		return self


	def __idiv__(self, that):
		if isinstance(that, Output):
			self.enabled.__idiv__(that.enabled)

		else:
			self.enabled.__idiv__(that)

		return self


	def __itruediv__(self, that):
		if isinstance(that, Output):
			self.enabled.__itruediv__(that.enabled)

		else:
			self.enabled.__itruediv__(that)

		return self


	def __neg__(self):
		return -self.enabled


	def __pos__(self):
		return +self.enabled


	def __abs__(self):
		return abs(self.enabled)


	def __invert__(self):
		return ~self.enabled


	def __complex__(self):
		return complex(self.enabled)


	def __int__(self):
		return int(self.enabled)


	def __long__(self):
		return long(self.enabled)


	def __float__(self):
		return float(self.enabled)


	def __oct__(self):
		return oct(self.enabled)


	def __hex__(self):
		return hex(self.enabled)


	def __index__(self):
		return self.enabled.__index__()


	def __coerce__(self, that):
		if isinstance(that, Output):
			return self.enabled.__coerce__(that.enabled)

		else:
			return self.enabled.__coerce__(that)


class Outputs(object):
	"""
	Wraps the MPD outputs into a convenient class.
	"""

	def __init__(self, mpd):
		self.mpd = mpd

	def __getitem__(self, index_name):
		"""
		Returns a single output. Can be used with either the name or the id
		of the output.
		"""
		return self.get(index_name)


	def get(self, index_name):
		"""
		Returns a single output. Can be used with either the name or the id
		of the output.
		"""
		if isinstance(index_name, (int, long)):
			index = index_name

		else:
			index = self.index(index_name)


		return Output(index, self.mpd)


	def index(self, name):
		"""
		Returns the ID of the given output name.
		"""
		return self.names().index(name)	

	
	def name(self, index):
		"""
		Returns the name of the given output ID.
		"""
		return self.names()[index]

	
	def __setitem__(self, index_name, value):
		"""
		Sets the output (by name or ID) to be enabled or not.
		"""
		if isinstance(index_name, (int, long)):
			index = index_name
		else:
			index = self.index(index_name)

		output = Output(index, self.mpd)

		output.enabled = value


	def __len__(self):
		"""
		Returns the number of defined outputs for MPD.
		"""
		return len(self.mpd._conn.outputs())


	def __iter__(self):
		"""
		Returns an iterator over the outputs. This always is in the order
		of output ID's.
		"""
		return self.itervalues()


	def __contains__(self, name):
		"""
		Returns whether the given name is in the set of defined MPD outputs.
		"""
		return self.has_name(name)


	def has_name(self, name):
		"""
		Returns whether the given name is in the set of defined MPD outputs.
		"""
		return name in self.names()


	def names(self):
		"""
		Returns the list of names, in ID order.
		"""
		return [name for name, value in self.items()]


	def iternames(self):
		"""
		Returns an iterator over the output names. This always is in the order
		of output ID's.
		"""
		return self.names().__iter__()


	def values(self):
		"""
		Returns the list of outputs, in ID order.
		"""
		return [value for name, value in self.items()]


	def itervalues(self):
		"""
		Returns an iterator over the outputs. This always is in the order of output ID's.
		"""
		return self.values().__iter__()


	def items(self):
		"""
		Returns a list of two-part tuples, in (name, output) format, in the order of ID's.
		"""
		outputs = self.mpd._conn.outputs()
		ret = []
		for i in xrange(len(outputs)):
			ret.append(False)

		for output in outputs:
			output_id = int(output["outputid"])
			ret[output_id] = (output["outputname"], Output(output_id, self.mpd))

		return ret


	def iteritems(self):
		"""
		Returns an iterator over the list of two-part tuples (name, output), in order of
		output ID's.
		"""
		return self.items().__iter__()



	def __repr__(self):
		"""
		Returns a more readable form of the list of outputs, in the format:

		{"<name1>": <enabled1>, "<name2>": <enabled2>}

		This always lists the outputs in ID order.
		"""
		conv = []
		for i in xrange(self.__len__()):
			conv.append("")

		output_id = 0
		for name, enabled in self.iteritems():
			conv[output_id] = repr(Output(output_id, self.mpd))
			output_id += 1

		ret = "{"

		conv_iter = iter(conv)

		try:
			ret += conv_iter.next()

		except StopIteration:
			pass

		try:
			while True:
				ret += ", " + conv_iter.next()

		except StopIteration:
			ret += "}"

		return ret


	def __str__(self):
		"""
		Returns a more readable form of the list of outputs, in the format:

		[<name1>: <enabled1>, <name2>: <enabled2>]

		This always lists the outputs in ID order.
		"""
		conv = []
		for i in xrange(self.__len__()):
			conv.append("")

		output_id = 0
		for name, enabled in self.iteritems():
			conv[output_id] = str(Output(output_id, self.mpd))
			output_id += 1

		ret = "["

		conv_iter = iter(conv)

		try:
			ret += conv_iter.next()

		except StopIteration:
			pass

		try:
			while True:
				ret += ", " + conv_iter.next()

		except StopIteration:
			ret += "]"

		return ret


@total_ordering
class _MPD(object):
	@property
	def DECODERS(self):
		"""
		Returns all of the decoders that are available for MPD.
		"""

		if not hasattr(self, "_DECODERS"):

			ret = _decoders()
        
			for decoder in self._conn.decoders():
				d = _decoder()
				for attr, value in decoder.iteritems():
					if attr != "plugin":
						setattr(d, attr, value)
        
				setattr(ret, decoder["plugin"], d)
        
			self._DECODERS = ret

		return self._DECODERS


	@property
	def URL_HANDLERS(self):
		"""
		Returns all of the URL handlers available for MPD.
		"""
		return self._conn.urlhandlers()


	@property
	def COMMANDS(self):
		"""
		Returns the list of commands that the MPD object can use.
		"""
		return self._conn.commands()


	@property
	def NOT_COMMANDS(self):
		"""
		Returns the list of commands that the MPD object cannot use.
		"""
		return self._conn.notcommands()


	@property
	def TAG_TYPES(self):
		"""
		Returns an object which has properties/attributes for each valid tag type used in MPD.
		"""
		if not hasattr(self, "_TAG_TYPES"):
			def _tag_typer(cls):
				for tag in MPD_API.tagtypes():
					setattr(cls, tag.lower(), tag)

				return cls

			@_tag_typer
			class _tagtypes(object):
				pass

			self._TAG_TYPES = _tagtypes()

		return self._TAG_TYPES


	@property
	def LIST_TYPES(self):
		"""
		Returns an object which has properties/attributes for each valid type to use in the MPD.all_files.list() command.
		"""
		if not hasattr(self, "_LIST_TYPES"):
			self._LIST_TYPES = _tagtypes()
			setattr(self._LIST_TYPES, "file", "file")

		return self._LIST_TYPES


	@property
	def FIND_TYPES(self):
		"""
		Returns an object which has properties/attributes for each valid type to use in the MPD.all_files.find() command.
		"""
		if not hasattr(self, "_FIND_TYPES"):
			self._FIND_TYPES = self._tagtypes()
			setattr(self._FIND_TYPES, "file", "file")
			setattr(self._FIND_TYPES, "any", "any")

		return self._FIND_TYPES


	REPLAY_GAIN = _REPLAY_GAIN

	SUBSYSTEMS = _SUBSYSTEMS

	def _track(self, filename):
		return Track(filename)


	@property
	def _conn(self):
		"""
		Returns the current object used for running the MPD commands.
		"""
		if not hasattr(self, "_conn_stack"):
			self._conn_stack = [MPD_API]

		return self._conn_stack[0]


	@_conn.setter
	def _conn(self, new_conn):
		"""
		Pushes a new object onto the MPD connection stack.
		"""
		if not hasattr(self, "_conn_stack"):
			self._conn_stack = [MPD_API]

		self._conn_stack.insert(0, new_conn)


	@_conn.deleter
	def _conn(self):
		"""
		Removes the top object from the MPD connection stack. The default connection object will always remain.
		"""
		if not hasattr(self, "_conn_stack"):
			self._conn_stack = [MPD_API]

		del self._conn_stack[0]

		if len(self._conn_stack) == 0:
			self._conn_stack = [MPD_API]


	@property
	def command_list(self):
		"""
		Allows for a number of commands to be executed together, can improve performance greatly. Designed to be used
		within the python with statement:
		
		with MPD.command_list:
			MPD.crossfade = 2
			MPD.repeat = False
			MPD.random = False

		Will configure these settings in a single MPD update, while:

		MPD.crossfade = 2
		MPD.repeat = False
		MPD.random = False

		Will do them individually.
		"""

		return CommandList(self)

	@property
	def all_files(self):
		"""
		Returns the list of files that exist in the MPD server.
		"""

		if not hasattr(self, "_all_files"):
			self._all_files = Database(self)

		return self._all_files


	def _info(self, filename):
		"""
		Gets the information of a given file in the MPD server.
		"""
		return self._conn.find("file", filename)[0]


	@property
	def pause(self):
		"""
		Returns whether the playlist is paused or not.

		Setting this to either True or False will pause/unpause the current playlist.

		Note: At any time, a playlist can be either playing, stopped, or paused, but never more than one of these.
		"""
		return self.status['state'] == 'pause'


	@pause.setter
	def pause(self, do_pause):
		"""
		Sets the playlist to paused or not.
		"""
		self._conn.pause(do_pause == True)


	@property
	def current(self):
		"""
		Returns the path/filename for the currently playing song.
		"""
		ret = self._conn.currentsong()
		if "file" in ret:
			return self._track(ret["file"])
		else:
			return None

	@property
	def play(self):
		"""
		Returns whether the playlist is playing or not.

		Setting this to a path/filename for a track will start playing that track in the playlist.

		Note: At any time, a playlist can be either playing, stopped, or paused, but never more than one of these.
		"""
		return self.status['state'] == 'play'


	@play.setter
	def play(self, track):
		"""
		Starts the playlist to play a given song.
		"""
		if track == True:
			self._conn.play()
		elif isinstance(track, (int, long)):
			track += 1

			if track < 1:
				track += self.__len__()
			
			self._conn.play(track)

		else:
			track = self.index(track)

			self._conn.play(track)


	@property
	def stop(self):
		"""
		Returns whether the playlist is stopped or not.

		Setting this to True will stop the playlist from playing.

		Note: At any time, a playlist can be either playing, stopped, or paused, but never more than one of these.
		"""
		return self.status['state'] == 'stop'


	@stop.setter
	def stop(self, do_stop):
		"""
		Stops playback.
		"""
		self._conn.stop()


	def seek(self, time):
		"""
		Seeks to a given time within the current song.
		"""

		self._conn.seek(self._conn.status()["song"], time)


	def __iadd__(self, time):
		"""
		Seeks forward in the currently playing song, by the number of seconds given.

		This is equivalent to self.seek(+<time>)
		"""
		if time >= 0:
			self.seek("+{time}".format(time = time))
		else:
			self.seek("{time}".format(time = time))


	def __isub__(self, time):
		"""
		Seeks backward in the currently playing song, but the number of seconds given.

		This is equivalent to self.seek(-<time>)
		"""
		if time >= 0:
			self.seek("{time}".format(time = time))
		else:
			self.seek("+{time}".format(time = time))


	@property
	def crossfade(self):
		"""
		Returns the crossfade setting.

		Setting this to an integer will set the crossfade to that value in seconds.
		"""
		return self.status['crossfade']


	@crossfade.setter
	def crossfade(self, seconds):
		"""
		Sets crossfading (mixing) between songs.
		"""
		self._conn.crossfade(int(seconds))


	@property
	def single(self):
		"""
		Returns the Single setting.

		Setting this to True or False will turn the Single setting on or off.
		"""
		return self.status['single']


	@single.setter
	def single(self, do_single):
		"""
		Toggles the single mode on or off.
		"""
		self._conn.single(int(do_single))


	@property
	def consume(self):
		"""
		Returns the Consume setting.

		Setting this to True or False will turn the Consume setting on or off.
		"""
		return self.status['consume']

	@consume.setter
	def consume(self, do_consume):
		"""
		Toggles the consume mode on or off.
		"""
		self._conn.consume(int(do_consume))

	@property
	def random(self):
		"""
		Returns the Random setting.

		Setting this to True or False will turn the Random setting on or off.
		"""
		return self.status['random']

	@random.setter
	def random(self, do_random):
		"""
		Sets 'random' mode, which plays the playlist in random order.
		"""
		self._conn.random(int(do_random))


	@property
	def repeat(self):
		"""
		Returns the Repeat setting.

		Setting this to True or False will turn the Repeat setting on or off.
		"""
		return self.status['repeat']


	@repeat.setter
	def repeat(self, do_repeat):
		"""
		Repeat the playlist after all songs have been used.
		"""
		self._conn.repeat(int(do_repeat))

	
	@property
	def replay_gain(self):
		"""
		Returns the replay gain mode.

		This can be set using the properties of the mpd_connector.REPLAY_GAIN class:

		MPD.replay_gain = REPLAY_GAIN.track
		"""
		return self._conn.replay_gain_status()


	@replay_gain.setter
	def replay_gain(self, mode):
		"""
		Sets the replay gain mode.

		This can be set using the REPLAIN_GAIN object in mpd_connector.
		"""
		self._conn.replay_gain_mode(mode)


	@property
	def playlist(self):
		"""
		Returns the current playlist, as a set of file/paths for each song in order.

		Setting this with the name of an External Playlist will load that playlist as the
		current playlist. See help(MPD.playlists) for more information on External Playlists.
		"""
		return [self._track(song["file"]) for song in self._conn.playlistinfo()]


	@playlist.setter
	def playlist(self, name):
		"""
		Will load the given external playlist by name as the current playlist.
		"""
		self._conn.load(name)


	@property
	def playlists(self):
		"""
		Contains all of the external playlists. This functions as a dictionary, with the keys being
		the names of all of the current external playlists. Each external playlist works similarly to
		a list.  This allows for simple access to all of the playlists stored in MPD's playlist folder.

		The current playlist can be saved and added to this object by using the MPD.save() method.

		An external playlist can be loaded into being the current playlist by assigning the name of
		the external playlist to the MPD.playlist property.

		External playlists are hashable, relying on their names as what is hashed. As such, the name property
		cannot be changed.

		Comparison operators are based on the elements of each external playlist, as would be expected.

		See help(MPD.playlists) for more information.

		Also see help(MPD.playlists["<name>"]) for further information, replacing <name> with the name of
		an external playlist.
		"""

		if not hasattr(self, "_external_playlists"):
			self._external_playlists = Playlists()

		return self._external_playlists


	def save(self, name):
		"""
		Saves the current playlist as an external playlist, stored in the MPD playlist folder.

		The file will be named <name>.m3u
		"""
		self._conn.save(name)


	@property
	def outputs(self):
		"""
		This property manages the list of outputs defined for MPD. It is possible to interact with the outputs
		directly, either by name or by index (output ID). Enabling and disabling outputs is as simple as simply
		assigning True or False to the given output.

		The list of outputs functions similarly, but not completely like a dict. There is no keys, rather all
		outputs can be selected by name or by index (output ID).

		See help(MPD.outputs) for more information.
		"""
		if not hasattr(self, "_outputs"):
			self._outputs = Outputs(self)
		return self._outputs


	@property
	def update(self):
		"""
		Returns True if the MPD server is currently updating its database.

		Setting this as:

		update = True

		will initiate a full update of the DB.


		Setting this as:

		update = <path>

		will initiate an update to just the tracks within the given path.
		"""
		return 'updating_db' in self.status

	@update.setter
	def update(self, path):
		"""
		Tells the MPD server to update itself.
		"""
		if str(path) == "True":
			self._conn.update()

		else:
			self._conn.update(path)


	def rescan(self, path = None):
		"""
		Tells the MPD server to do a full rescan, including all unmodified files.
		"""
		if path == None:
			self._conn.rescan()

		else:
			self._conn.rescan(path)


	def configure(self):
		"""
		Configures the MPD server's playlist to work in a manner this application prefers.
		The configuration is defined in settings.yaml under mpd->config.
		"""
		with self.command_list:
			for attr, value in _config.iteritems():
				setattr(self, attr, value)


	def __getitem__(self, index):
		"""
		Gets a single track from the current playlist as its file/path.
		"""
		index = int(index)
		if index < 0:
			index += self.__len__()

			if index < 0:
				index = 0

		return self._track(self._conn.playlistinfo(index)[0]["file"])


	def __setitem__(self, index, track):
		"""
		Sets a single track in the playlist. Give the filename with full path relative to the music root.
		"""
		index = int(index)
		if index < 0:
			index += self.__len__()

			if index < 0:
				index = 0

		with self.command_list:
			self._conn.delete(index)
			self._conn.addid(track, index)
		

	def __delitem__(self, index):
		"""
		Deletes a single track from the playlist.
		"""
		index = int(index)
		if index < 0:
			index += self.__len__()

			if index < 0:
				index = 0

		self._conn.delete(index)


	def __iter__(self):
		"""
		Returns an iterator for the current playlist.
		"""
		return self.playlist.__iter__()


	def __len__(self):
		"""
		Returns the length of the current playlist.
		"""
		return self.status['playlistlength']


	def __eq__(self, other):
		"""
		Tests if the MPD's current playlist equals the given value.
		"""

		if isinstance(other, _MPD):
			return self.playlist == other.playlist

		else:
			return self.playlist == other

	
	def __ne__(self, other):
		"""
		Tests if the MPD's current playlist does not equal the given value.
		"""

		if isinstance(other, _MPD):
			return self.playlist != other.playlist

		else:
			return self.playlist != other

	
	def __ge__(self, other):
		"""
		Tests if the MPD's current playlist is greater than or equal to the given value.
		"""

		if isinstance(other, _MPD):
			return self.playlist >= other.playlist

		else:
			return self.playlist >= other

	
	def __contains__(self, track):
		"""
		Determines if the track (file/path) is in the current playlist or not.
		"""
		return track in self.playlist


	def extend(self, tracklist):
		"""
		Extends the current playlist with the given list of filenames/paths.
		"""
		for track in tracklist:
			self._conn.add(track)

	def insert(self, index, track):
		"""
		Inserts the given track (file/path) into the current playlist at the given position.
		"""
		index = int(index)
		if index < 0:
			index += self.__len__()
			if index < 0:
				index = 0

		self._conn.addid(track, index)

	def append(self, track):
		"""
		Adds the given track to the end of the current playlilst.
		"""
		self._conn.add(track)


	def index(self, track, i = 0, j = None):
		"""
		Finds the index of the given track(file/path) in the current playlist.
		"""
		if j == None:
			j = len(self)
		return self.playlist.index(track, i, j)


	def count(self, track):
		"""
		Counts the occurences of the given track (file/path) in the current playlist.
		"""
		return self.playlist.count(track)


	def shuffle(self):
		"""
		Shuffles/randomizes the order of the current playlist.
		"""
		self._conn.shuffle()


	def swap(self, pos1, pos2):
		"""
		Exchanges the song at pos1 with the song at pos2 in the current playlist.
		"""
		self._conn.swap(pos1, pos2)


	@property
	def status(self):
		"""
		Returns the current status of the MPD server.

		Note -- some "housecleaning" is done that makes this a bit nicer to use with the _MPD class.
		"""
		ret = self._conn.status()

		for key in ret:
			if key in ('random', 'repeat', 'single', 'consume'):
				ret[key] = int(ret[key]) == 1
			elif key in ('playlistlength', 'xfade', 'playlist', 'updating_db', 'bitrate', 'song', 'nextsong') or (key in ('mixrampdelay') and ret[key] != "nan"):
				ret[key] = int(ret[key])
			elif key in ('elapsed', 'mixrampdb'):
				ret[key] = float(ret[key])
			elif key in ('mixrampdelay') and ret[key] == 'nan':
				ret[key] = None
			elif key in ('time'):
				ret[key] = tuple([int(value) for value in ret[key].split(":")])

		ret['crossfade'] = ret['xfade']

		if ret['state'] != 'stop':
			ret['current'] = self[ret['song']]

			if 'nextsong' in ret:
				ret['next'] = self[ret['nextsong']]
			else:
				ret['next'] = None

		if 'xfade' in ret:
			del ret['xfade']

		if 'volume' in ret:
			del ret['volume']

		if 'playlist' in ret:
			del ret['playlist']

		if 'songid' in ret:
			del ret['songid']

		if 'nextsongid' in ret:
			del ret['nextsongid']
		

		return ret

	def ping(self):
		"""
		Pings the MPD server.
		"""
		return self._conn.ping()


	def next(self):
		"""
		Starts playing the next track in the current playlist.
		"""
		self._conn.next()


	def previous(self):
		"""
		Starts playing the previous track in the current playlist.
		"""
		self._conn.previous()


	def __str__(self):
		"""
		Returns the MPD server status.
		"""
		return str(self.status)


	def __repr__(self):
		"""
		Returns the string representation of the current playlist.
		"""
		return repr(self.playlist)


	def clear(self):
		"""
		Clears the current playlist.
		"""
		self._conn.clear()


	@property
	def mixrampdb(self):
		"""
		Returns the MixRamp dB setting, in deciBels.

		Can be set by simply assigning a new deciBel value to this setting.
		"""
		return self.status['mixrampdb']


	@mixrampdb.setter
	def mixrampdb(self, decibels):
		self._conn.mixrampdb(decibels)


	@property
	def mixrampdelay(self):
		"""
		Returns the MixRamp Delay setting, in seconds.

		Can be set by simply assigning a new number of seconds to this setting.
		"""
		return self.status['mixrampdelay']


	@mixrampdelay.setter
	def mixrampdelay(self, seconds):
		if seconds == None:
			self._conn.mixrampdelay("nan")
		else:
			self._conn.mixrampdelay(seconds)


	@property
	def audio(self):
		"""
		Returns the audio information of the currently playing song, if there is a currently playing song.
		"""
		if "audio" in self.status:
			return self.status["audio"]
		else:
			return None


	@property
	def time(self):
		"""
		Returns the time information, in (played, total) format, the two values are numbers of seconds for the currently playing song.
		"""
		if "time" in self.status:
			return self.status["time"]
		else:
			return None


	@property
	def bitrate(self):
		"""
		Returns the bitrate information for the currently playing song.
		"""
		if "bitrate" in self.status:
			return self.status["bitrate"]
		else:
			return None


	@property
	def elapsed(self):
		"""
		Returns the total number of seconds that have elapsed playing the current song.
		"""
		if "elapsed" in self.status:
			return self.status["elapsed"]
		else:
			return None


MPD = _MPD()


if __name__ == "__main__":
	with LogWriter():
		MPD.play()

		MPD.configure()
